package MySQL::Query::Tables;

use strict;
use warnings;

use base qw(MySQL::Query);
__PACKAGE__->mk_accessors(qw(dbh tree data tables parse_tree count cur_num));

use Carp;
use Tie::IxHash;
use MySQL::Query::Aliases;
use MySQL::Describe;

my $token_class   = 'DBIx::MyParsePP::Token';
my $rule_class    = 'DBIx::MyParsePP::Rule';

sub parse_tree     { return shift->tree->tree }

sub init {
      my $self = shift;
      
      $self->cur_num(0);
      
      croak "No tree object"  unless $self->tree;
      croak "No parse tree"   unless $self->parse_tree; 
	
      my %tables = ();
      tie %tables, "Tie::IxHash";

      my $from = $self->parse_tree->getFrom();
      $from = $from->shrink;
      $from = $from->extract('table_factor' , 'opt_table_alias');
      $from = [$from] if (ref($from) ne 'ARRAY');
      
      # all tables with/without aliasies
      my $all_tables = $self->parse_tree->getTables();
      # getTables() should be improved to get tables in right order.
      %tables = map { $_->value => "" } @{$all_tables};
      
      for my $table (@{$from}) {
	    next if !$table;
            
            my $alias_name;
            if (ref $table->[2] eq $token_class) {
		  $alias_name = $table->[2]->value;
            } else {
		  $alias_name = $table->[2]->[2]->value; 
            }
            
            unless ($tables{$table->[1]->value}) {
                  $tables{$table->[1]->value} = MySQL::Query::Aliases->new(table => $table->[1]->value);
            } 
                  
            $tables{$table->[1]->value}->add($alias_name);
            delete $tables{$alias_name} if exists $tables{$alias_name} and length $alias_name; #delete it from table list, becasue it's an alias
      }
      
      $self->data(\%tables);
      $self->count(scalar keys %tables);
      my @names = keys %tables;
      $self->tables(\@names);
      
      return $self->tables;
}

sub alias {
      my ($self , $table) = @_;
      unless ($table) {
            # get table name from iterator
            $table = $self->tables->[$self->cur_num - 1];
      } 
      return $self->{data}->{$table};
}

sub aliases {
      my $self = shift;
      my @list = ();
      for (@{$self->tables}) {
            my $alias = $self->alias($_);
            push @list , @{$alias->list} if $alias;
      }
      return \@list;
}

sub table_by_alias {
      my ($self , $alias) = @_;
      
      my $tables = $self->tables;
      foreach my $table (@$tables) {
            my $alias_o = $self->alias($table);
                  next unless $alias_o;
            my $list = $alias_o->list;
            if (grep { $_ eq $alias } @$list) {
                  return $table;            
            }
      }
}

sub next {
      my $self = shift;
	
      croak "get_tables() should be called before next_table()" unless $self->tables;
      if ($self->cur_num >= $self->count) {
	    $self->cur_num(0); # auto flush 
	    return; 
      }
      	
      my $table = $self->tables->[$self->cur_num];
      $self->cur_num($self->cur_num + 1);
	
      return $table;
}

sub uniq {
      my $self = shift;
      
      my @uniq = ();
      my $tables = $self->tables;
      my $aliases = $self->aliases;
      
      foreach my $table (@$tables) {
            my $alias = $self->alias($table);
            if ($alias) {
                  push @uniq , @{$alias->list};
            } else {
                  push @uniq , $table;
            }
      }
      
      return \@uniq;
}

sub describe {
      my ($self , $table) = @_;
      
      croak "No database handle found\n" unless $self->dbh;
      my $desc = MySQL::Describe->new( dbh => $self->dbh );
      
      $desc->desc($table);
      return $desc;
}


=head1 NAME

MySQL::Query::Tables - Getting tables info from parsed SQL tree

=head1 SYNOPSIS
      
      my $tables = MySQL::Query::Tables->new(tree => $tree, dbh => $dbh);
      
      #getting table names
      my @tables = $tables->get_tables();
      
      #number of tables
      my $tables_number = $tables->tables_num();
      
      #alias of a table if any
      my $t1_alias = $tables->alias($tables[0]);
      
=head1 DESCRIPTION

      Module makes it simple to extract tables and aliases from parsed SQL tree.
      This is an alpha realese. The following methods is subject to change in future.

=head1 METHODS

=head2 new(%config)
      
      Created new object:
            my $tables = MySQL::Query::Tables->new( tree => $tree , dbh => $dbh);
            tree is mandatory, it should be MySQL::Query object
            dbh is mandatory only if you want to use describe_table() method
      
=head2 tree()
      
      Returns MySQL::Query object
            my $tree_object = $tables->tree;
      
=head2 parse_tree()

      Returns DBIx::MyParsePP object
            my $myparse = $tables->parse_tree;
            
=head2 dbh()

      Returns DBI handle if it was specified when constracting an object
            my $dbh = $tables->dbh;

=head2 tables()

      Returns internal tables hash, keys are table names and values are arrayrefs to aliases
            my $internal_t = $tables->tables;

=head2 tables_num()

      Returns number of tables detected
            my $tables_num = $tables->tables_num;

=head2 get_tables()

      This method should be called after constracting an object if you want
      to work with tables and their aliases. It returns table names
            my @tables = $tables->get_tables();

=head2 next_table()

      It's some form of iterator over the internal tables structure.
      while (my $table = $tables->next_table) {
            ... do something ...
      }

=head2 table_alias($tables)
      
      $tables is an arrayref to a table names.
      Returns a list of aliases
            my $aliases = $tables->table_alias(['table_name' , 'one_more_table']);

=head2 alias($table)

      Same as prev.

=head2 aliases($tables)

      $tables is an arrayref to a table names.
      The difference between table_alias() and aliases() is that table_alias() returns a list of aliases
      and aliases() return a list of list.
            my $aliases = $table->aliases(['table1' , 'table2' , 'table3']);
            #print Dumper $aliases
            #[ ['table1_alias1', 'table1_alias2'] , "" , ['table3_alias1'] ];

=head2 describe_table($table)
      
      Returns table description from database
            my $descr = $table->describe_table
      
=head1 AUTHOR

Mikhail Maluyk <mikhail.maluyk@gmail.com>
1;
